package com.hqd.jjvm.classformat.attributeinfo;

import com.hqd.jjvm.classformat.ClassFile;
import com.hqd.jjvm.util.HexStrTransformUtil;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
@NoArgsConstructor
public class AnnotationAttributeInfo extends AttributeInfo {

	public AnnotationAttributeInfo(Integer attributeNameIndex, Integer attributeLength) {
		super(attributeNameIndex, attributeLength);
	}

	static class ElementValuePair {
		private Integer elementNameIndex;
		private ElementValuePair.ElementValue elementValue;

		public ElementValuePair(Integer elementNameIndex) {
			this.elementNameIndex = elementNameIndex;
		}

		public ElementValuePair(Integer elementNameIndex, ElementValuePair.ElementValue elementValue) {
			this.elementNameIndex = elementNameIndex;
			this.elementValue = elementValue;
		}

		@Getter
		static class ElementValue {
			private String tag;
			private Integer constValueIndex;
			private ElementValuePair.EnumConstValue enumConstValue;
			private Integer classInfoIndex;
			private ElementValuePair.Annotation annotation;
			private ElementValuePair.ArrayValue arrayValue;

			public ElementValue(String tag) {
				this.tag = tag;
			}

			public ElementValue(String tag, Integer constValueIndex, Integer classInfoIndex) {
				this.tag = tag;
				this.constValueIndex = constValueIndex;
				this.classInfoIndex = classInfoIndex;
			}

			public ElementValue(String tag, ElementValuePair.EnumConstValue enumConstValue) {
				this.tag = tag;
				this.enumConstValue = enumConstValue;
			}

			public ElementValue(String tag, ElementValuePair.Annotation annotation) {
				this.tag = tag;
				this.annotation = annotation;
			}

			public ElementValue(String tag, ElementValuePair.ArrayValue arrayValue) {
				this.tag = tag;
				this.arrayValue = arrayValue;
			}
		}

		@Getter
		static class Annotation {
			private Integer typeIndex;
			private Integer valueLength;
			private List<ElementValuePair> elementValuePairList;

			public Annotation(Integer typeIndex, Integer valueLength, List<ElementValuePair> elementValuePairList) {
				this.typeIndex = typeIndex;
				this.valueLength = valueLength;
				this.elementValuePairList = elementValuePairList;
			}
		}

		@Getter
		static class ArrayValue {
			private Integer numOfValue;
			private List<ElementValuePair.ElementValue> elementValueList;

			public ArrayValue(Integer numOfValue, List<ElementValuePair.ElementValue> elementValueList) {
				this.numOfValue = numOfValue;
				this.elementValueList = elementValueList;
			}
		}

		@Getter
		static class EnumConstValue {
			private Integer typeNameIndex;
			private Integer constNameIndex;

			EnumConstValue(Integer typeNameIndex, Integer constNameIndex) {
				this.typeNameIndex = typeNameIndex;
				this.constNameIndex = constNameIndex;
			}
		}
	}

	protected List<ElementValuePair.Annotation> parseAnnotation(ClassFile classFile, Integer annotationLength) {
		List<ElementValuePair.Annotation> annotations = new ArrayList<>(annotationLength);
		for (int i = 0; i < annotationLength; i++) {
			Integer typeIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
			Integer pairLength = HexStrTransformUtil.parseHexToInt(classFile.readU2());
			ElementValuePair.Annotation annotation = new ElementValuePair.Annotation(typeIndex, pairLength, parseElementValuePairs(classFile, pairLength));
			annotations.add(annotation);
		}
		return annotations;
	}

	protected List<ElementValuePair> parseElementValuePairs(ClassFile classFile, int pairLength) {
		List<ElementValuePair> elementValuePairList = new ArrayList<>(pairLength);
		for (int i = 0; i < pairLength; i++) {
			ElementValuePair elementValuePair = new ElementValuePair(HexStrTransformUtil.parseHexToInt(classFile.readU2()));
			elementValuePair.elementValue = parseElementValue(classFile);
			elementValuePairList.add(elementValuePair);
		}
		return elementValuePairList;
	}

	protected ElementValuePair.ElementValue parseElementValue(ClassFile classFile) {
		String tag = HexStrTransformUtil.pareHexToStr(classFile.readU1());
		ElementValuePair.ElementValue elementValue = new ElementValuePair.ElementValue(tag);
		if ("BCDFIJSZs".contains(tag)) {
			elementValue.constValueIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
		} else if (tag.equals("e")) {
			Integer typeNameIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
			Integer constNameIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
			ElementValuePair.EnumConstValue enumConstValue = new ElementValuePair.EnumConstValue(typeNameIndex, constNameIndex);
			elementValue.enumConstValue = enumConstValue;
		} else if (tag.equals("c")) {
			elementValue.classInfoIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
		} else if (tag.equals("@")) {
			Integer typeIndex = HexStrTransformUtil.parseHexToInt(classFile.readU2());
			Integer valueLength = HexStrTransformUtil.parseHexToInt(classFile.readU2());
			List<ElementValuePair> pairs = parseElementValuePairs(classFile, valueLength);
			elementValue.annotation = new ElementValuePair.Annotation(typeIndex, valueLength, pairs);
		} else if (tag.equals("[")) {
			Integer valueLength = HexStrTransformUtil.parseHexToInt(classFile.readU2());
			List<ElementValuePair.ElementValue> arrayValueList = new ArrayList<>(valueLength);
			for (int i = 0; i < valueLength; i++) {
				arrayValueList.add(parseElementValue(classFile));
			}
			elementValue.arrayValue = new ElementValuePair.ArrayValue(valueLength, arrayValueList);
		}
		return elementValue;
	}
}
